package com.xayy.core.antlr4.visitor;

import cn.hutool.core.util.StrUtil;
import com.xayy.core.antlr4.exception.ExpressException;
import com.xayy.core.antlr4.model.TelemetryVo;
import com.xayy.core.antlr4.model.VisitorResult;
import com.xayy.core.antlr4.parsing.HealthEvaluateBaseVisitor;
import com.xayy.core.antlr4.parsing.HealthEvaluateParser;
import com.xayy.core.antlr4.utils.*;
import lombok.extern.slf4j.Slf4j;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;

import java.util.*;
import java.util.function.BiPredicate;
import java.util.function.Function;

/**
 * @author Nie
 * @version V1.0
 * @date 2023/9/4 13:37
 */
@Slf4j
public class HealthEvaluateVisitor extends HealthEvaluateBaseVisitor<VisitorResult> {

    /**
     * 规则定义的参数
     */
    private final Map<String, Object> paramDefine = new HashMap<>();

    public Boolean checkSyntax = false;
    public Boolean errorEvaluation = false;
    public Long satelliteId = null;
    public Long nodeId = null;
    public Date evaluationTime = null;

    @Override
    public VisitorResult visitFatorBool(HealthEvaluateParser.FatorBoolContext ctx) {
        String text = ctx.BOOL().getText();
        boolean b = Boolean.parseBoolean(text);
        return VisitorResult.value(b);
    }

    public HealthEvaluateVisitor() {
    }

    public HealthEvaluateVisitor(Long satelliteId, Long nodeId, Date evaluationTime, Boolean checkSyntax,
                                 Boolean errorEvaluation) {
        this.satelliteId = satelliteId;
        this.nodeId = nodeId;
        this.evaluationTime = evaluationTime;
        this.checkSyntax = checkSyntax;
        this.errorEvaluation = errorEvaluation;
    }

    private <T extends ParserRuleContext> VisitorResult visitDrill(T node) {
        Function<T, VisitorResult> vr = (cc) -> {
            log.debug("进入[" + node.getText() + "]处理逻辑");
            VisitorResult childResult = null;
            for (int i = 0; i < cc.getChildCount(); i++) {
                ParseTree child = cc.getChild(i);
                if (StrUtil.equalsAny(child.getText(), ";", "\n", "\r\n", "(", ")", "{", "}", "function()", "<EOF>")) {
                    continue;
                }
                childResult = visit(child);
            }
            return childResult;
        };
        return vr.apply(node);
    }

    @Override
    public VisitorResult visitMain(HealthEvaluateParser.MainContext ctx) {
        return visitDrill(ctx);
    }

    @Override
    public VisitorResult visitCommand(HealthEvaluateParser.CommandContext ctx) {
        return visitDrill(ctx);
    }

    @Override
    public VisitorResult visitProg(HealthEvaluateParser.ProgContext ctx) {
        return visitDrill(ctx);
    }

    @Override
    public VisitorResult visitMathFun(HealthEvaluateParser.MathFunContext ctx) {
        int type = ctx.type.getType();
        switch (type) {
            case HealthEvaluateParser.MAX: {
                VisitorResult visit = visit(ctx.paramCode());
                List<Double> list = visit.getList();
                Double max = list.stream().max(Comparator.comparing(x -> x)).orElse(null);
                return VisitorResult.value(max);
            }
            case HealthEvaluateParser.MIN: {
                VisitorResult visit = visit(ctx.paramCode());
                List<Double> list = visit.getList();
                Double min = list.stream().min(Comparator.comparing(x -> x)).orElse(null);
                return VisitorResult.value(min);
            }
            case HealthEvaluateParser.AVG: {
                VisitorResult visit = visit(ctx.paramCode());
                List<Double> list = visit.getList();
                double avg = list.stream().mapToDouble(Double::doubleValue).average().getAsDouble();
                return VisitorResult.value(avg);
            }
            default: {
                log.error("没有获取到对应的函数");
                return VisitorResult.nll();
            }
        }
    }

    @Override
    public VisitorResult visitCondition(HealthEvaluateParser.ConditionContext ctx) {
        // if处理逻辑
        VisitorResult visit = visit(ctx.ifs());
        if (visit.getBool()) {
            return visitDrill(ctx.ifblock);
        }

        // elseif处理逻辑
        for (int i = 0; i < ctx.elif().size(); i++) {
            VisitorResult elifResult = visit(ctx.elif().get(i));
            if (elifResult.getBool()) {
                return visitDrill(ctx.command().get(i + 1));
            }
        }

        if (Objects.nonNull(ctx.elseblock)) {
            // else处理逻辑
            return visitDrill(ctx.elseblock);
        }
        throw new ExpressException("无else分支，且if分支和elseif分支条件均不满足");
    }

    @Override
    public VisitorResult visitIfs(HealthEvaluateParser.IfsContext ctx) {
        return visit(ctx.compare());
    }

    @Override
    public VisitorResult visitElif(HealthEvaluateParser.ElifContext ctx) {
        return visit(ctx.compare());
    }

    @Override
    public VisitorResult visitNumarg(HealthEvaluateParser.NumargContext ctx) {
        VisitorResult visit = visit(ctx.calcu());
        paramDefine.put(ctx.id.getText(), visit.getNumber());
        return VisitorResult.value(visit.getValue());
    }

    @Override
    public VisitorResult visitChararg(HealthEvaluateParser.CharargContext ctx) {
        String valueText = ctx.value.getText();
        paramDefine.put(ctx.id.getText(), valueText);
        return VisitorResult.value(valueText);
    }

    @Override
    public VisitorResult visitBoolarg(HealthEvaluateParser.BoolargContext ctx) {
        VisitorResult visit = visit(ctx.compare());
        paramDefine.put(ctx.id.getText(), visit.getBool());
        return VisitorResult.value(visit.getBool());
    }

    @Override
    public VisitorResult visitAnd(HealthEvaluateParser.AndContext ctx) {
        return VisitorResult.value(logic(ctx.compare(0), ctx.compare(1), (t, u) -> t && u));
    }

    @Override
    public VisitorResult visitOr(HealthEvaluateParser.OrContext ctx) {
        return VisitorResult.value(logic(ctx.compare(0), ctx.compare(1), (t, u) -> t || u));
    }

    @Override
    public VisitorResult visitExre(HealthEvaluateParser.ExreContext ctx) {
        return visit(ctx.expr());
    }

    @Override
    public VisitorResult visitBool(HealthEvaluateParser.BoolContext ctx) {
        return VisitorResult.value("true".equals(ctx.BOOL().getText()));
    }

    private boolean logic(ParserRuleContext left, ParserRuleContext right, BiPredicate<Boolean, Boolean> func) {
        VisitorResult v1 = visit(left);
        VisitorResult v2 = visit(right);
        return func.test(v1.getBool(), v2.getBool());
    }

    @Override
    public VisitorResult visitDoubleSymbol(HealthEvaluateParser.DoubleSymbolContext ctx) {
        int operatorLType = ctx.operatorL.getType();
        int operatorRType = ctx.operatorR.getType();
        Double a = visit(ctx.fator(0)).getNumber();
        Double b = visit(ctx.fator(1)).getNumber();
        Double c = visit(ctx.fator(2)).getNumber();
        return VisitorResult.value(compare(operatorLType, a, b) && compare(operatorRType, b, c));
    }

    private boolean compare(int symbol, double a, double b) {
        switch (symbol) {
            case HealthEvaluateParser.EQ:
                return a == b;
            case HealthEvaluateParser.NEQ:
                return a != b;
            case HealthEvaluateParser.LT:
                return a < b;
            case HealthEvaluateParser.LE:
                return a <= b;
            case HealthEvaluateParser.GT:
                return a > b;
            case HealthEvaluateParser.GE:
                return a >= b;
            default:
                throw new ExpressException("未知的比判符号");
        }
    }

    @Override
    public VisitorResult visitEq(HealthEvaluateParser.EqContext ctx) {
        Double left = visit(ctx.fator(0)).getNumber();
        Double right = visit(ctx.fator(1)).getNumber();
        int compare = Double.compare(left, right);
        return VisitorResult.value(compare == 0);
    }

    @Override
    public VisitorResult visitNoteq(HealthEvaluateParser.NoteqContext ctx) {
        Double left = visit(ctx.fator(0)).getNumber();
        Double right = visit(ctx.fator(1)).getNumber();
        int compare = Double.compare(left, right);
        return VisitorResult.value(compare != 0);
    }

    @Override
    public VisitorResult visitLt(HealthEvaluateParser.LtContext ctx) {
        Double left = visit(ctx.fator(0)).getNumber();
        Double right = visit(ctx.fator(1)).getNumber();
        int compare = Double.compare(left, right);
        return VisitorResult.value(compare < 0);
    }

    @Override
    public VisitorResult visitLe(HealthEvaluateParser.LeContext ctx) {
        Double left = visit(ctx.fator(0)).getNumber();
        Double right = visit(ctx.fator(1)).getNumber();
        int compare = Double.compare(left, right);
        return VisitorResult.value(compare <= 0);
    }

    @Override
    public VisitorResult visitGt(HealthEvaluateParser.GtContext ctx) {
        Double left = visit(ctx.fator(0)).getNumber();
        Double right = visit(ctx.fator(1)).getNumber();
        int compare = Double.compare(left, right);
        return VisitorResult.value(compare > 0);
    }

    @Override
    public VisitorResult visitGe(HealthEvaluateParser.GeContext ctx) {
        Double left = visit(ctx.fator(0)).getNumber();
        Double right = visit(ctx.fator(1)).getNumber();
        int compare = Double.compare(left, right);
        return VisitorResult.value(compare >= 0);
    }

    @Override
    public VisitorResult visitFatorExpr(HealthEvaluateParser.FatorExprContext ctx) {
        return visit(ctx.expr());
    }

    @Override
    public VisitorResult visitFatorParam(HealthEvaluateParser.FatorParamContext ctx) {
        return visit(ctx.calcu());
    }

    @Override
    public VisitorResult visitParens(HealthEvaluateParser.ParensContext ctx) {
        return visit(ctx.calcu());
    }

    @Override
    public VisitorResult visitMulAndDiv(HealthEvaluateParser.MulAndDivContext ctx) {
        Double left = visit(ctx.calcu(0)).getNumber();
        Double right = visit(ctx.calcu(1)).getNumber();
        if (ctx.opt.getType() == HealthEvaluateParser.MUL) {
            return VisitorResult.value(left * right);
        } else {
            return VisitorResult.value(left / right);
        }
    }

    @Override
    public VisitorResult visitAddAndSub(HealthEvaluateParser.AddAndSubContext ctx) {
        Double left = visit(ctx.calcu(0)).getNumber();
        Double right = visit(ctx.calcu(1)).getNumber();
        if (ctx.opt.getType() == HealthEvaluateParser.ADD) {
            return VisitorResult.value(left + right);
        } else {
            return VisitorResult.value(left - right);
        }
    }

    @Override
    public VisitorResult visitTandemLeft(HealthEvaluateParser.TandemLeftContext ctx) {
        Double left = visit(ctx.calcu(0)).getNumber();
        Double right = visit(ctx.calcu(1)).getNumber();
        return VisitorResult.value(left * right);
    }

    @Override
    public VisitorResult visitTandemRight(HealthEvaluateParser.TandemRightContext ctx) {
        Double left = visit(ctx.calcu(0)).getNumber();
        Double right = visit(ctx.calcu(1)).getNumber();
        return VisitorResult.value(left * right);
    }


    @Override
    public VisitorResult visitGn(HealthEvaluateParser.GnContext ctx) {
        Double value = GnUtil.getGnScore(this.satelliteId, this.nodeId, this.evaluationTime, ctx.GN().getText(),
                this.checkSyntax, this.errorEvaluation);
        return VisitorResult.value(value);
    }


    @Override
    public VisitorResult visitXn(HealthEvaluateParser.XnContext ctx) {
        Double value = XnUtil.getXnScore(this.satelliteId, this.nodeId, this.evaluationTime, ctx.XN().getText(),
                this.checkSyntax, this.errorEvaluation);
        return VisitorResult.value(value);
    }

    @Override
    public VisitorResult visitJk(HealthEvaluateParser.JkContext ctx) {
        Double value = JkUtil.getJkScore(this.satelliteId, this.nodeId, this.evaluationTime, ctx.JK().getText(),
                this.checkSyntax, this.errorEvaluation);
        return VisitorResult.value(value);
    }

    @Override
    public VisitorResult visitParam(HealthEvaluateParser.ParamContext ctx) {
        Double value = CsUtil.getCSScore(this.satelliteId, this.nodeId, this.evaluationTime, ctx.PARAM().getText(),
                this.checkSyntax, this.errorEvaluation);
        return VisitorResult.value(value);
    }

    @Override
    public VisitorResult visitNumber(HealthEvaluateParser.NumberContext ctx) {
        double aDouble = Double.parseDouble(ctx.NUMBER().getText());
        return VisitorResult.value(aDouble);
    }

    @Override
    public VisitorResult visitTelemetry(HealthEvaluateParser.TelemetryContext ctx) {
        return visit(ctx.paramCode());
    }

    @Override
    public VisitorResult visitCalcuAbs(HealthEvaluateParser.CalcuAbsContext ctx) {
        Double number = visit(ctx.abs()).getNumber();
        return VisitorResult.value(number);
    }

    @Override
    public VisitorResult visitAbsNumber(HealthEvaluateParser.AbsNumberContext ctx) {
        String text = ctx.NUMBER().getText();
        double aDouble = Double.parseDouble(text);
        double abs = Math.abs(aDouble);
        return VisitorResult.value(abs);
    }

    @Override
    public VisitorResult visitAbsCalcu(HealthEvaluateParser.AbsCalcuContext ctx) {
        Double number = visit(ctx.calcu()).getNumber();
        double abs = Math.abs(number);
        return VisitorResult.value(abs);
    }


    @Override
    public VisitorResult visitParamCode(HealthEvaluateParser.ParamCodeContext ctx) {
        String paramCode = ctx.TELEMETRY().getText();
        List<TerminalNode> number = ctx.NUMBER();
        // 仿真时长
        String durationStr = number.get(0).getText();
        int duration = Integer.parseInt(durationStr);
        TelemetryVo telemetryVo =
                TelemetryVo.builder().satelliteId(this.satelliteId).nodeId(this.nodeId).evaluationTime(this.evaluationTime)
                        .checkSyntax(this.checkSyntax).paramCode(paramCode).duration(duration).build();
        if (number.size() > 1) {
            // 仿真步长
            String stepStr = number.get(1).getText();
            int step = Integer.parseInt(stepStr);
            telemetryVo.setStep(step);
        }
        List<Double> selectTelemetry = TelemetryUtil.selectTelemetry(telemetryVo);
        return VisitorResult.value(selectTelemetry);
    }

}
